#!/usr/bin/perl
use FindBin;
use lib $FindBin::Bin;
use lib "$FindBin::Bin/lib";
use lib "$FindBin::Bin/../lib";

use strict;
use Getopt::Long;

#use Getopt::Long qw(GetOptionsFromArray);
use POSIX qw(uname);
use File::Basename;
use File::Spec;
use Cwd qw(abs_path);
use JSON qw(from_json to_json);

#use JSON;
use ProcessFinder;
use OSGather;
use ConnGather;
use CollectObjCat;

use AutoExecUtils;

Getopt::Long::Configure qw(gnu_getopt);
Getopt::Long::Configure("pass_through");

my $COLLECT_TIMEOUT = 300;
my $START_TIME      = time();
$ENV{TS_CMDB_AUTOCOLLECT} = $START_TIME;

sub collectTimeout {
    my $osType = ( uname() )[0];
    $osType =~ s/\s.*$//;

    print("ERROR: Collect information for current process timeout, exceed $COLLECT_TIMEOUT seconds.\n");

    if ( $osType ne 'Windows' ) {
        my @pids = `ps eww|grep TS_CMDB_AUTOCOLLECT=$START_TIME|grep -v grep|awk '{print \$1}'`;
        foreach my $pid (@pids) {
            if ( $pid ne $$ ) {
                kill($pid);
            }
        }
        sleep(3);
        my @pids = `ps eww|grep TS_CMDB_AUTOCOLLECT=$START_TIME|grep -v grep|awk '{print \$1}'`;
        foreach my $pid (@pids) {
            if ( $pid ne $$ ) {
                kill( 'KILL', $pid );
            }
        }
    }
    else {
        #TODO: 需要实现根据环境变量查找进程的powershell脚本
    }
}

#加载各个Collector，调用getConfig方法获取Process的filter配置
sub getProcessFilters {
    my ($collectClassMap) = @_;

    my @procFilters = ();
    for my $collectorPmPath ( glob("$FindBin::Bin/*Collector.pm") ) {
        my $collectorName = basename($collectorPmPath);
        if ( $collectorName eq 'DemoCollector.pm' or $collectorName eq 'BaseCollector.pm' ) {
            next;
        }

        my $collectClass = substr( $collectorName, 0, -3 );
        my $objType      = substr( $collectorName, 0, -12 );

        if ( defined($collectClassMap) and not defined( $collectClassMap->{$objType} ) ) {
            next;
        }

        eval {
            require($collectorName);
            my $filter = $collectClass->getConfig();
            if ( defined( $filter->{regExps} ) and ( not defined( $filter->{enabled} ) or $filter->{enabled} == 1 ) ) {
                if ( not defined( $filter->{seq} ) ) {
                    $filter->{seq} = 100;
                }
                $filter->{objType}   = $objType;
                $filter->{className} = $collectClass;
                push( @procFilters, $filter );
            }
        };
        if ($@) {
            print("WARN: $@\n");
        }
    }

    #使用filter的seq值进行排序，控制先后匹配的次序，用于standalone应用的匹配
    @procFilters = sort { $a->{seq} <=> $b->{seq} } @procFilters;

    return \@procFilters;
}

#收集OS和硬件信息
sub collectHostOSInfo {
    my ( $osType, $collectClassMap, $ignoreOsInfo, $inspect ) = @_;

    my ( $hostInfo, $osInfo );
    if ( not defined($collectClassMap) or defined( $collectClassMap->{OS} ) ) {
        print("INFO: Begin to collect $osType OS information...\n");
        my $osGather = OSGather->new( $ignoreOsInfo, $inspect );
        ( $hostInfo, $osInfo ) = $osGather->collect();
        if ( defined($hostInfo) ) {
            my $hostObjCat = CollectObjCat->get('HOST');
            $hostInfo->{_OBJ_CATEGORY} = $hostObjCat;
            $hostInfo->{_OBJ_TYPE}     = 'HOST';
            $hostInfo->{OS_ID}         = $osGather->{osId};
            $hostInfo->{MGMT_IP}       = $osGather->{mgmtIp};
            $hostInfo->{MGMT_PORT}     = $osGather->{mgmtPort};
            $hostInfo->{PK}            = CollectObjCat->getPK($hostObjCat);
        }
        else {
            $hostInfo = {};
        }

        if ( defined($osInfo) ) {
            my $osObjCat = CollectObjCat->get('OS');
            $osInfo->{_OBJ_CATEGORY} = $osObjCat;
            $osInfo->{_OBJ_TYPE}     = $osType;
            $osInfo->{OS_TYPE}       = $osType;
            $osInfo->{OS_ID}         = $osGather->{osId};
            $osInfo->{MGMT_IP}       = $osGather->{mgmtIp};
            $osInfo->{MGMT_PORT}     = $osGather->{mgmtPort};
            $osInfo->{PK}            = CollectObjCat->getPK($osObjCat);

            #适配disk单独作为模型并且拥有一个ip字段
            if ( defined( $osInfo->{DISKS} ) ) {
                my $disks = $osInfo->{DISKS};
                foreach my $disk (@$disks) {
                    $disk->{_OBJ_CATEGORY} = 'OS';
                    $disk->{_OBJ_TYPE}     = 'OS-DISK';
                }
            }

            #适配mount单独作为模型并且拥有一个ip字段
            if ( defined( $osInfo->{MOUNT_POINTS} ) ) {
                my $mounts = $osInfo->{MOUNT_POINTS};
                foreach my $mount (@$mounts) {
                    $mount->{_OBJ_CATEGORY} = 'OS';
                    $mount->{_OBJ_TYPE}     = 'OS-MOUNT';
                }
            }

            if ( defined($hostInfo) and defined( $hostInfo->{BOARD_SERIAL} ) ) {
                $osInfo->{HOST_ON} = [
                    {
                        '_OBJ_CATEGORY' => 'HOST',
                        '_OBJ_TYPE'     => 'HOST',
                        'BOARD_SERIAL'  => $hostInfo->{BOARD_SERIAL},
                        'HOST_IP'       => $hostInfo->{MGMT_IP}
                    }
                ];
            }
            else {
                $osInfo->{HOST_ON} = [];
            }
        }
        else {
            $osInfo = {};
        }

        print("INFO: OS information collected.\n");
    }

    return ( $osInfo, $hostInfo );
}

#处理存在父子关系的进程的连接信息，并合并到父进程
# sub mergeMultiProcs {
#     my ( $pFinder, $osInfo, $appsArray, $appsMap, $ipAddrs, $ipv6Addrs, $inspect ) = @_;

#     my $pidToDel = {};
#     my @apps     = ();
#     print("INFO: Begin to merge connection information with parent processes...\n");
#     foreach my $pid ( keys(%$appsMap) ) {
#         my $info = $appsMap->{$pid};
#         if ( not defined( $info->{_MULTI_PROC} ) ) {
#             next;
#         }

#         my $procInfo = $info->{PROC_INFO};

#         my $parentInfo = $appsMap->{ $procInfo->{PPID} };

#         my $currentInfo = $info;
#         my $objCat      = $currentInfo->{_OBJ_CATEGORY};
#         my $objType     = $currentInfo->{_OBJ_TYPE};

#         my $parentTopInfo;
#         while ( defined($parentInfo) and $parentInfo->{_OBJ_CATEGORY} eq $objCat and $parentInfo->{_OBJ_TYPE} eq $objType ) {
#             $parentTopInfo = $parentInfo;
#             $currentInfo   = $parentInfo;
#             $parentInfo    = $appsMap->{ $currentInfo->{PROC_INFO}->{PPID} };
#         }

#         if ( defined($parentTopInfo) ) {
#             $parentTopInfo->{CPU_USAGE} = $parentTopInfo->{CPU_USAGE} + $procInfo->{CPU_USAGE};
#             $parentTopInfo->{MEM_USAGE} = $parentTopInfo->{MEM_USAGE} + $procInfo->{MEM_USAGE};
#             $parentTopInfo->{MEM_USED}  = $parentTopInfo->{MEM_USED} + $procInfo->{MEM_USED};

#             if ( index( $pid, '-' ) < 0 ) {
#                 my $maxOpenFilesCount = $pFinder->getProcMaxOpenFilesCount($pid);
#                 my $openFilesCount    = $pFinder->getProcOpenFilesCount($pid);
#                 my $openFilesRate     = 0;
#                 if ( defined($maxOpenFilesCount) and $maxOpenFilesCount > 0 ) {
#                     $openFilesRate = int( $openFilesCount * 10000 / $maxOpenFilesCount ) / 100;
#                 }

#                 my $openFilesInfo = $info->{OPEN_FILES_INFO};
#                 if ( not defined($openFilesInfo) ) {
#                     $openFilesInfo = [];
#                     $info->{OPEN_FILES_INFO} = $openFilesInfo;
#                 }
#                 push( @$openFilesInfo, { PID => $pid, OPEN => $openFilesCount, MAX => $maxOpenFilesCount, RATE => $openFilesRate } );
#             }

#             $parentTopInfo->{OPEN_FILES_COUNT} = $parentTopInfo->{OPEN_FILES_COUNT} + $pFinder->getProcOpenFilesCount($pid);

#             my $parentConnInfo  = $parentTopInfo->{PROC_INFO}->{CONN_INFO};
#             my $currentConnInfo = $procInfo->{CONN_INFO};

#             my $parentLsnInfo = $parentConnInfo->{LISTEN};
#             map { $parentLsnInfo->{$_} = 1 } keys( %{ $currentConnInfo->{LISTEN} } );

#             #把基于端口统计的显式、隐式监听IP合并到父进程
#             my $portInfoMap       = $currentConnInfo->{PORT_BIND};
#             my $parentPortInfoMap = $parentConnInfo->{PORT_BIND};
#             while ( my ( $port, $portInfo ) = each(%$portInfoMap) ) {
#                 my $parentPortInfo = $parentPortInfoMap->{$port};
#                 while ( my ( $key, $ipMap ) = each(%$portInfo) ) {
#                     map { $parentPortInfo->{$key}->{$_} = 1 } keys(%$ipMap);
#                 }
#             }

#             my $parentPeerInfo = $parentConnInfo->{PEER};
#             map { $parentPeerInfo->{$_} = 1 } keys( %{ $currentConnInfo->{PEER} } );

#             #连接统计数据的合并
#             my $parentConnStats  = $parentConnInfo->{STATS};
#             my $currentConnStats = $currentConnInfo->{STATS};

#             $parentConnStats->{TOTAL_COUNT}       = $parentConnStats->{TOTAL_COUNT} + $currentConnStats->{TOTAL_COUNT};
#             $parentConnStats->{INBOUND_COUNT}     = $parentConnStats->{INBOUND_COUNT} + $currentConnStats->{INBOUND_COUNT};
#             $parentConnStats->{OUTBOUND_COUNT}    = $parentConnStats->{OUTBOUND_COUNT} + $currentConnStats->{OUTBOUND_COUNT};
#             $parentConnStats->{SYN_RECV_COUNT}    = $parentConnStats->{SYN_RECV_COUNT} + $currentConnStats->{SYN_RECV_COUNT};
#             $parentConnStats->{CLOSE_WAIT_COUNT}  = $parentConnStats->{CLOSE_WAIT_COUNT} + $currentConnStats->{CLOSE_WAIT_COUNT};
#             $parentConnStats->{RECV_QUEUED_COUNT} = $parentConnStats->{RECV_QUEUED_COUNT} + $currentConnStats->{RECV_QUEUED_COUNT};
#             $parentConnStats->{SEND_QUEUED_COUNT} = $parentConnStats->{SEND_QUEUED_COUNT} + $currentConnStats->{SEND_QUEUED_COUNT};
#             $parentConnStats->{RECV_QUEUED_SIZE}  = $parentConnStats->{RECV_QUEUED_SIZE} + $currentConnStats->{RECV_QUEUED_SIZE};
#             $parentConnStats->{SEND_QUEUED_SIZE}  = $parentConnStats->{SEND_QUEUED_SIZE} + $currentConnStats->{SEND_QUEUED_SIZE};

#             if ( $inspect == 1 ) {

#                 #基于调用OutBound（目标）的统计信息合并
#                 my $parentOutBoundStats  = $parentConnStats->{OUTBOUND_STATS};
#                 my $currentOutBoundStats = $currentConnStats->{OUTBOUND_STATS};
#                 while ( my ( $remoteAddr, $outBoundStat ) = each(%$currentOutBoundStats) ) {
#                     my $parentOutBoundStat = $parentOutBoundStats->{$remoteAddr};
#                     $parentOutBoundStat->{OUTBOUND_COUNT}   = $parentOutBoundStat->{OUTBOUND_COUNT} + $outBoundStat->{OUTBOUND_COUNT};
#                     $parentOutBoundStat->{SEND_QUEUED_SIZE} = $parentOutBoundStat->{SEND_QUEUED_SIZE} + $outBoundStat->{SEND_QUEUED_SIZE};
#                     $parentOutBoundStat->{SYN_SENT_COUNT}   = $parentOutBoundStat->{SYN_SENT_COUNT} + $outBoundStat->{SYN_SENT_COUNT};
#                 }
#             }
#             $pidToDel->{$pid} = 1;

#         }
#         elsif ( index( $pid, '-' ) < 0 ) {
#             my $maxOpenFilesCount = $pFinder->getProcMaxOpenFilesCount($pid);
#             my $openFilesCount    = $pFinder->getProcOpenFilesCount($pid);
#             my $openFilesRate     = 0;
#             if ( defined($maxOpenFilesCount) and $maxOpenFilesCount > 0 ) {
#                 $openFilesRate = int( $openFilesCount * 10000 / $maxOpenFilesCount ) / 100;
#             }
#             $info->{OPEN_FILES_INFO} = [ { PID => $pid, OPEN => $openFilesCount, MAX => $maxOpenFilesCount, RATE => $openFilesRate } ];
#         }
#     }
#     print("INFO: Connection information merged.\n");

#     #抽取所有的top层的进程，并对CONN_INFO信息进行整理，转换为数组的格式
#     #while ( my ( $pid, $appInfo ) = each(%$appsMap) ) {
#     foreach my $appInfo (@$appsArray) {
#         my $pid = $appInfo->{PROC_INFO}->{PID};
#         if ( not defined( $pidToDel->{$pid} ) ) {
#             my $procInfo = $appInfo->{PROC_INFO};
#             my $connInfo = {};
#             if ( defined($procInfo) ) {
#                 $connInfo = $procInfo->{CONN_INFO};
#             }

#             my @lsnStats    = ();
#             my @lsnPorts    = ();
#             my @appLsnPorts = ();
#             while ( my ( $lsnAddr, $backlogQ ) = each( %{ $connInfo->{LISTEN} } ) ) {
#                 push( @lsnPorts,    $lsnAddr );
#                 push( @appLsnPorts, { ADDR => $lsnAddr } );
#                 push( @lsnStats,    { ADDR => $lsnAddr, QUEUED => $backlogQ } );
#             }
#             $connInfo->{LISTEN} = \@lsnPorts;
#             if ( not defined( $appInfo->{LISTEN} ) ) {
#                 $appInfo->{LISTEN} = \@appLsnPorts;
#             }

#             my $minPort      = 65535;
#             my $insPort      = $appInfo->{PORT};
#             my $portsMap     = {};
#             my $bindAddrsMap = {};
#             my @bindAddrs    = ();
#             if ( defined($insPort) and $insPort ne '' ) {
#                 $portsMap->{$insPort} = 1;
#             }
#             foreach my $lsnPort (@lsnPorts) {
#                 if ( $lsnPort !~ /:\d+$/ ) {
#                     $lsnPort = int($lsnPort);
#                     if ( $lsnPort < $minPort ) {
#                         $minPort = $lsnPort;
#                     }
#                     $portsMap->{$lsnPort} = 1;
#                     foreach my $ipInfo (@$ipAddrs) {
#                         $bindAddrsMap->{"$ipInfo->{IP}:$lsnPort"} = 1;
#                     }
#                     foreach my $ipInfo (@$ipv6Addrs) {
#                         $bindAddrsMap->{"$ipInfo->{IP}:$lsnPort"} = 1;
#                     }
#                 }
#                 else {
#                     $bindAddrsMap->{$lsnPort} = 1;
#                     push( @bindAddrs, $lsnPort );
#                     my $myPort = $lsnPort;
#                     $myPort =~ s/^.*://;
#                     $myPort = int($myPort);
#                     if ( $myPort < $minPort ) {
#                         $minPort = $myPort;
#                     }
#                     $portsMap->{$myPort} = 1;
#                 }
#             }
#             @bindAddrs = keys(%$bindAddrsMap);
#             $connInfo->{BIND} = \@bindAddrs;

#             #把connInfo中的PEER信息，区分开local ip和远端IP，分开存放
#             my @localPeerAddrs  = ();
#             my @remotePeerAddrs = ();
#             foreach my $rAddr ( keys( %{ $connInfo->{PEER} } ) ) {
#                 if ( $rAddr =~ /^127\./ or $rAddr =~ /^::1:/ ) {
#                     push( @localPeerAddrs, $rAddr );
#                 }
#                 else {
#                     push( @remotePeerAddrs, $rAddr );
#                 }
#             }
#             $connInfo->{PEER}       = \@remotePeerAddrs;
#             $connInfo->{LOCAL_PEER} = \@localPeerAddrs;
#             delete( $procInfo->{CONN_INFO} );

#             #TCP连接统计信息中的比率指标统计
#             my $connStats = $connInfo->{STATS};
#             if ( $connStats->{TOTAL_COUNT} > 0 ) {
#                 $connStats->{RECV_QUEUED_RATE}     = int( $connStats->{RECV_QUEUED_COUNT} * 10000 / $connStats->{TOTAL_COUNT} + 0.5 ) / 100;
#                 $connStats->{SEND_QUEUED_RATE}     = int( $connStats->{SEND_QUEUED_COUNT} * 10000 / $connStats->{TOTAL_COUNT} + 0.5 ) / 100;
#                 $connStats->{RECV_QUEUED_SIZE_AVG} = int( $connStats->{RECV_QUEUED_SIZE} * 100 / $connStats->{TOTAL_COUNT} + 0.5 ) / 100;
#                 $connStats->{SEND_QUEUED_SIZE_AVG} = int( $connStats->{SEND_QUEUED_SIZE} * 100 / $connStats->{TOTAL_COUNT} + 0.5 ) / 100;
#             }

#             #TCP OutBound连接的比率指标统计
#             if ( $inspect == 1 ) {
#                 while ( my ( $remoteAddr, $outBoundStat ) = each( %{ $connStats->{OUTBOUND_STATS} } ) ) {
#                     if ( $outBoundStat->{OUTBOUND_COUNT} > 0 ) {
#                         $outBoundStat->{SEND_QUEUED_RATE}     = int( $outBoundStat->{SEND_QUEUED_RATE} * 10000 / $outBoundStat->{OUTBOUND_COUNT} + 0.5 ) / 100;
#                         $outBoundStat->{SEND_QUEUED_SIZE_AVG} = int( $outBoundStat->{SEND_QUEUED_SIZE} * 100 / $outBoundStat->{OUTBOUND_COUNT} + 0.5 ) / 100;
#                     }
#                 }
#             }

#             #重新整理连接统计数据，从CONN_INFO中抽离出来CONN_STATS和CONN_OUTBOUND_STATS
#             my @outBoundStats = ();
#             while ( my ( $remoteAddr, $outBoundStat ) = each( %{ $connStats->{OUTBOUND_STATS} } ) ) {
#                 $outBoundStat->{REMOTE_ADDR} = $remoteAddr;
#                 push( @outBoundStats, $outBoundStat );
#             }
#             delete( $connStats->{OUTBOUND_STATS} );

#             if ( scalar(@bindAddrs) > 0 ) {
#                 $appInfo->{CONN_OUTBOUND_STATS} = \@outBoundStats;
#                 my $inBoundStats = delete( $connInfo->{STATS} );
#                 if ( defined($inBoundStats) ) {
#                     $appInfo->{CONN_STATS} = $inBoundStats;
#                 }
#                 else {
#                     $appInfo->{CONN_STATS} = [];
#                 }

#                 $appInfo->{LISTEN_STATS} = \@lsnStats;

#                 $appInfo->{CONN_INFO} = $connInfo;

#                 if ( $minPort < 65535 and not defined( $appInfo->{PORT} ) ) {
#                     $appInfo->{PORT} = $minPort;
#                 }

#                 #把SERVICE_PORTS格式从Map转换为可以支持导入的数组类型(应用实例提供的服务)
#                 my $insObjCat   = CollectObjCat->get('INS');
#                 my $dbInsObjCat = CollectObjCat->get('DBINS');
#                 my $objCat      = $appInfo->{_OBJ_CATEGORY};

#                 my @servicePortsArray = ();
#                 my $servicePorts      = $appInfo->{SERVICE_PORTS};
#                 if ( defined($servicePorts) ) {

#                     #如果存在服务端口，则加入SERVICE_PORTS对象
#                     while ( my ( $svcName, $svcPort ) = each(%$servicePorts) ) {
#                         push(
#                             @servicePortsArray,
#                             {
#                                 _OBJ_CATEGORY => $objCat,
#                                 _OBJ_TYPE     => 'Service-Ports',
#                                 NAME          => $svcName,
#                                 PORT          => $svcPort
#                             }
#                         );
#                     }
#                     $appInfo->{SERVICE_PORTS} = \@servicePortsArray;
#                 }
#                 if ( $objCat eq $insObjCat or $objCat eq $dbInsObjCat ) {
#                     $appInfo->{SERVICE_PORTS} = \@servicePortsArray;

#                     #如果是应用实例，补充其他监听的端口（未知协议或服务名）
#                     foreach my $svcPort ( keys(%$portsMap) ) {
#                         if ( not defined( $servicePorts->{$svcPort} ) ) {
#                             push(
#                                 @servicePortsArray,
#                                 {
#                                     _OBJ_CATEGORY => $objCat,
#                                     _OBJ_TYPE     => 'Service-Ports',
#                                     NAME          => $svcPort,
#                                     PORT          => $svcPort
#                                 }
#                             );
#                         }
#                     }
#                 }
#             }

#             #估算主业务IP和VIP，如果有特殊情况
#             #需要定制修改ProcessFinder的方法predictBizIp（应用的VIP和主业务IP）, OSGatherBase的方法getBizIp（主机业务IP）
#             if ( not defined( $appInfo->{PRIMARY_IP} ) or not defined( $appInfo->{VIP} ) ) {
#                 my ( $bizIp, $vip ) = $pFinder->predictBizIp( $connInfo, $minPort );
#                 if ( not defined( $appInfo->{PRIMARY_IP} ) ) {
#                     $appInfo->{PRIMARY_IP} = $bizIp;
#                 }
#                 if ( not defined( $appInfo->{VIP} ) ) {
#                     $appInfo->{VIP} = $vip;
#                 }
#             }

#             $appInfo->{UPTIME} = $procInfo->{ELAPSED};

#             #如果是非进程类别的信息采集信息，则清除PROC_INFO
#             if ( delete( $appInfo->{NOT_PROCESS} ) ) {
#                 delete( $appInfo->{PROC_INFO} );
#             }
#             delete( $connInfo->{PORT_BIND} );

#             push( @apps, $appInfo );
#         }
#     }

#     return \@apps;
# }

# sub resolveSymlinks {
#     my ($path) = @_;

#     if ( -l $path ) {
#         my $target = readlink($path);

#         if ( !File::Spec->file_name_is_absolute($target) ) {
#             $target = File::Spec->rel2abs( $target, dirname($path) );
#         }
#         return resolveSymlinks($target);
#     }
#     elsif ( -e $path ) {
#         return abs_path($path);
#     }
#     else {
#         return undef;
#     }
# }

# sub getRealInstallPath {
#     my ( $appInfo, $procInfo ) = @_;

#     my $realInstallPath = '';
#     my $realBinPath;

#     my $installPath = $appInfo->{INSTALL_PATH};
#     if ( defined($installPath) and $installPath ne '' and -e $installPath ) {
#         $realBinPath = resolveSymlinks("$installPath/bin");
#         if ( not defined($realBinPath) ) {
#             $realBinPath = resolveSymlinks("$installPath/sbin");
#         }
#         if ( not defined($realBinPath) ) {
#             $realBinPath = resolveSymlinks("$installPath/lib");
#         }
#         if ( not defined($realBinPath) ) {
#             $realBinPath = resolveSymlinks("$installPath");
#         }
#     }
#     else {
#         my $binPath = $appInfo->{BIN_PATH};
#         if ( defined($binPath) and $binPath ne '' ) {
#             $realBinPath = resolveSymlinks($binPath);
#         }
#         else {
#             my $exePath = $appInfo->{EXE_PATH};
#             if ( defined($exePath) and $exePath ne '' ) {
#                 $binPath     = dirname($exePath);
#                 $realBinPath = resolveSymlinks($binPath);
#             }
#             else {
#                 my $executableFile = $procInfo->{EXECUTABLE_FILE};
#                 if ( defined($executableFile) and $executableFile ne '' ) {
#                     $binPath     = dirname($executableFile);
#                     $realBinPath = resolveSymlinks($binPath);
#                 }
#             }
#         }
#     }

#     if ( defined($realBinPath) and $realBinPath ne '' ) {
#         $realInstallPath = dirname($realBinPath);
#     }

#     return $realInstallPath;
# }

# #提供给ProcessFinder调用的回调函数，当进程信息匹配配置的过滤配置时就会调用此函数
# #此回调函数会初始化Collector类并调用其collect方法
# sub doDetailCollect {
#     my ( $collectorClass, $procInfo, $pFinder ) = @_;

#     #collectorClass: 收集器类名
#     #procInfo；ps的进程信息

#     #matchedProcsInfo：前面已经匹配上进程信息，用于多进程应用的连接去重
#     my $matchedProcsInfo = $pFinder->{matchedProcsInfo};
#     my $appsMap          = $pFinder->{appsMap};
#     my $appsArray        = $pFinder->{appsArray};
#     my $osType           = $pFinder->{ostype};
#     my $passArgs         = $pFinder->{passArgs};
#     my $osInfo           = $pFinder->{osInfo};
#     my $connGather       = $pFinder->{connGather};

#     print("INFO: Os type:$osType\n");
#     alarm($COLLECT_TIMEOUT);

#     my $isMatched = 0;
#     my $objCat;
#     my $pid     = $procInfo->{PID};
#     my $objType = $procInfo->{_OBJ_TYPE};

#     print("INFO: Process $pid matched filter:$objType, begin to collect data...\n");
#     my $connInfo;
#     if ( $osType eq 'Windows' and $procInfo->{COMMAND} =~ /^System\b/ ) {

#         #Windows System进程没有lisnten信息
#         $connInfo = { PEER => {}, LOCAL_PEER => {}, LISTEN => {} };
#     }
#     else {
#         $connInfo = $connGather->getListenInfo( $pid );
#         my $portInfoMap = $pFinder->getListenPortInfo( $connInfo->{LISTEN} );
#         $connInfo->{PORT_BIND} = $portInfoMap;
#         $procInfo->{CONN_INFO} = $connInfo;
#     }
#     print("INFO: Process connection infomation collected.\n");

#     my $collector;
#     my @appInfos = ();

#     eval {
#         $collector = $collectorClass->new( $passArgs, $pFinder, $procInfo, $matchedProcsInfo );
#         my @appInfosTmp = $collector->collect($procInfo);

#         foreach my $appInfo (@appInfosTmp) {
#             if ( defined($appInfo) and ref($appInfo) eq 'HASH' ) {
#                 push( @appInfos, $appInfo );
#             }
#         }
#         if ( scalar(@appInfos) > 0 ) {
#             $connInfo = $procInfo->{CONN_INFO};

#             #有些进程match并不是监听进程，可以通过设置procInfo的属性LISTENER_PID指定监听进程
#             my $statInfo = {};
#             my $lsnPid   = $procInfo->{LISTENER_PID};
#             if ( defined($lsnPid) and $lsnPid ne '' and $lsnPid ne $pid ) {
#                 my $lsnInfo = $connGather->getListenInfo( $lsnPid );
#                 map { $connInfo->{$_} = $lsnInfo->{$_} } keys(%$lsnInfo);
#                 my $portInfoMap = $pFinder->getListenPortInfo( $connInfo->{LISTEN} );
#                 $connInfo->{PORT_BIND} = $portInfoMap;

#                 $statInfo = $connGather->getStatInfo( $lsnPid, $connInfo->{LISTEN} );
#             }
#             else {
#                 $statInfo = $connGather->getStatInfo( $pid, $connInfo->{LISTEN} );
#             }
#             map { $connInfo->{$_} = $statInfo->{$_} } keys(%$statInfo);
#         }
#     };

#     if ($@) {
#         print("ERROR: $collectorClass return failed, $@\n");
#         return 0;
#     }

#     my $idx = 0;
#     for ( $idx = 0 ; $idx < scalar(@appInfos) ; $idx++ ) {
#         my $appInfo = $appInfos[$idx];
#         $isMatched = 1;

#         my $insObjCat   = CollectObjCat->get('INS');
#         my $dbInsObjCat = CollectObjCat->get('DBINS');

#         $objType = $appInfo->{_OBJ_TYPE};
#         if ( not defined($objType) ) {
#             $objType = $procInfo->{_OBJ_TYPE};
#             $appInfo->{_OBJ_TYPE} = $objType;
#         }

#         $objCat = $appInfo->{_OBJ_CATEGORY};
#         if ( not defined($objCat) or $objCat eq '' ) {
#             $objCat = $insObjCat;
#             $appInfo->{_OBJ_CATEGORY} = $objCat;
#         }
#         else {
#             if ( not CollectObjCat->validate( $appInfo->{_OBJ_CATEGORY} ) ) {
#                 print("WARN: Invalid object category: $appInfo->{_OBJ_CATEGORY}.\n");
#                 return 0;
#             }

#             #从实例采集信息中抽取出软件资产
#             if ( $objCat eq $insObjCat or $objCat eq $dbInsObjCat ) {
#                 my $softWare = {
#                     _OBJ_CATEGORY => $objCat,
#                     _OBJ_TYPE     => "Software-Asset",
#                     NAME          => $objType,
#                     VERSION       => $appInfo->{VERSION},
#                     INSTALL_PATH  => getRealInstallPath( $appInfo, $procInfo )
#                 };
#                 $appInfo->{SOFTWARE_ASSETS} = $softWare;
#             }

#             #从实例采集信息中抽取出服务，参考另外SERVICE_PORTS属性的处理
#         }

#         print("INFO: Matched Object Type:$objCat/$objType.\n");

#         $appInfo->{MGMT_IP}        = $procInfo->{MGMT_IP};
#         $appInfo->{MGMT_PORT}      = $procInfo->{MGMT_PORT};
#         $appInfo->{OS_ID}          = $procInfo->{OS_ID};
#         $appInfo->{OS_USER}        = $procInfo->{USER};
#         $appInfo->{_CONTAINERTYPE} = $procInfo->{_CONTAINERTYPE};

#         push( @$appsArray, $appInfo );

#         if ( $idx == 0 ) {
#             $appsMap->{ $procInfo->{PID} } = $appInfo;
#         }
#         else {
#             #如果出现多个appInfo同一个进程号的情况，则是但进程多对象的情况，需要处理PID为不一样的PID
#             if ( defined( $appsMap->{ $procInfo->{PID} } ) ) {
#                 my $realPid  = $procInfo->{REAL_PID};
#                 my $realPpid = $procInfo->{REAL_PPID};
#                 if ( not defined($realPid) ) {
#                     $realPid               = $procInfo->{PID};
#                     $realPpid              = $procInfo->{PPID};
#                     $procInfo->{REAL_PID}  = $realPid;
#                     $procInfo->{REAL_PPID} = $realPpid;
#                 }
#                 $procInfo->{PID}  = $procInfo->{REAL_PID} . '-' . $idx;
#                 $procInfo->{PPID} = $procInfo->{REAL_PPID} . '-' . $idx;
#             }
#             $appsMap->{ $procInfo->{PID} } = $appInfo;
#         }

#         my $notProcess = $appInfo->{NOT_PROCESS};
#         if ( not defined($notProcess) ) {
#             if ( not defined( $appInfo->{PROC_INFO} ) ) {
#                 $appInfo->{PROC_INFO} = $procInfo;
#             }
#             else {
#                 $procInfo = $appInfo->{PROC_INFO};
#             }

#             $appInfo->{PID}     = $procInfo->{PID};
#             $appInfo->{COMMAND} = $procInfo->{COMMAND};

#             my $cpuLogicCores = $osInfo->{CPU_LOGIC_CORES};
#             $appInfo->{CPU_LOGIC_CORES} = $cpuLogicCores;
#             if ( $cpuLogicCores > 0 ) {
#                 $appInfo->{CPU_USAGE} = int( ( $procInfo->{'%CPU'} + 0.0 ) * 100 / $cpuLogicCores ) / 100;
#             }
#             else {
#                 $appInfo->{CPU_USAGE} = $procInfo->{'%CPU'} + 0.0;
#             }

#             if ( $osType eq 'Windows' ) {
#                 $appInfo->{MEM_USED} = $procInfo->{MEMSIZE} + 0.0;
#                 if ( not defined( $appInfo->{MEM_USAGE} ) and $osInfo->{MEM_TOTAL} > 0 ) {
#                     $appInfo->{MEM_USAGE} = int( $appInfo->{MEM_SIZE} * 10000 / $osInfo->{MEM_TOTAL} ) / 100;
#                 }
#             }
#             else {
#                 $appInfo->{MEM_USED}  = int( ( $procInfo->{'%MEM'} + 0.0 ) * $osInfo->{MEM_TOTAL} ) / 100;
#                 $appInfo->{MEM_USAGE} = $procInfo->{'%MEM'} + 0.0;
#             }
#         }

#         my $envMap      = delete( $procInfo->{ENVIRONMENT} );
#         my $insNamePath = $envMap->{TS_INSNAME};
#         if ( defined($insNamePath) and $insNamePath ne '' ) {
#             my @insPaths = split( '/', $insNamePath );
#             if ( scalar(@insPaths) > 1 ) {
#                 $appInfo->{BELONG_APPLICATION} = [
#                     {
#                         _OBJ_CATEGORY => 'APPLICATION',
#                         _OBJ_TYPE     => 'APPLICATION',
#                         APP_NAME      => $insPaths[0],
#                     }
#                 ];
#                 $appInfo->{BELONG_APPLICATION_MODULE} = [
#                     {
#                         _OBJ_CATEGORY  => 'APPLICATION',
#                         _OBJ_TYPE      => 'APPLICATION_MODULE',
#                         APP_NAME       => $insPaths[0],
#                         APPMODULE_NAME => $insPaths[1],
#                     }
#                 ];
#             }
#             else {
#                 $appInfo->{BELONG_APPLICATION}        = [];
#                 $appInfo->{BELONG_APPLICATION_MODULE} = [];
#             }
#         }

#         if ( not defined( $appInfo->{PK} ) ) {
#             my $pkConfig = CollectObjCat->getPK($objCat);
#             if ( defined($pkConfig) ) {
#                 $appInfo->{PK} = $pkConfig;
#             }
#             else {
#                 $appInfo->{PK} = [ 'MGMT_IP', 'PORT' ];
#                 print("ERROR: $objType PK not defined for obj catetory:$objCat.\n");
#             }
#         }

#         my @envEntries = ();
#         while ( my ( $envName, $envVal ) = each(%$envMap) ) {
#             push( @envEntries, { NAME => $envName, VALUE => $envVal } );
#         }
#         if ( scalar(@envEntries) > 0 ) {
#             $appInfo->{MAIN_ENV} = \@envEntries;
#         }

#         #如果采集器自身未定义RUN_ON则自动添加
#         my $collectedRunOn = $appInfo->{RUN_ON};
#         if ( not defined($collectedRunOn) ) {
#             $appInfo->{RUN_ON} = [
#                 {
#                     '_OBJ_CATEGORY' => 'OS',
#                     '_OBJ_TYPE'     => $osType,
#                     'OS_ID'         => $procInfo->{OS_ID},
#                     'MGMT_IP'       => $procInfo->{MGMT_IP}
#                 }
#             ];
#         }
#         elsif ( scalar(@$collectedRunOn) == 0 ) {

#             #如果采集器定义了空的RUN_ON，代表不需要RUN_ON，可能是集群相关的采集，RUN_ON在多个OS上，无法全部采集
#             delete( $appInfo->{RUN_ON} );
#         }
#     }

#     return $isMatched;
# }

sub main {
    $ENV{LANG} = 'C';
    AutoExecUtils::setEnv();
    my $osInfo;
    my $osType = ( uname() )[0];
    $osType =~ s/\s.*$//;

    $SIG{ALRM} = \&collectTimeout;

    my $isVerbose  = 0;
    my $isDebug    = 0;
    my $inspect    = 0;
    my $container = 0;
    my $timeout    = 300;
    my $procEnvName;
    my $classDef;
    my $defaultPassConf;

    GetOptions(
        'verbose=i'         => \$isVerbose,
        'debug=i'           => \$isDebug,
        'timeout=i'         => \$timeout,
        'class=s'           => \$classDef,
        'inspect=i'         => \$inspect,
        'container=i'      => \$container,
        'procenvname=s'     => \$procEnvName,
        'defaultpassconf=s' => \$defaultPassConf
    );
    my @myOpts = @ARGV;

    if ( defined($isDebug) and $isDebug eq 1 ) {
        $ENV{OSCOLLECT_DEBUG} = 1;
    }

    if ( $timeout == 0 ) {
        $timeout = 300;
    }
    $COLLECT_TIMEOUT = $timeout;
    alarm($timeout);

    my $ignoreOsInfo = 0;
    my $collectClassMap;
    if ( defined($classDef) and $classDef ne '' ) {
        $collectClassMap = {};

        if ( $classDef =~ /^\[/ ) {
            my $classes = from_json($classDef);
            foreach my $class (@$classes) {
                $collectClassMap->{$class} = 1;
            }
        }
        else {
            map { $collectClassMap->{$_} = 1 } ( split( /\s*,\s*/, $classDef ) );
        }

        if ( not defined( $collectClassMap->{OS} ) ) {
            $ignoreOsInfo = 1;
            $collectClassMap->{OS} = 1;
        }
    }

    #拼装账户参数
    my $passArgs    = {};
    my $accountInfo = {};
    for ( my $i = 0 ; $i <= $#myOpts ; $i++ ) {
        my $item = $myOpts[$i];
        if ( substr( $item, 0, 1 ) eq '-' ) {
            $item =~ s/^--?//;
            my $optName = $item;
            my $optVal  = $myOpts[ $i + 1 ];
            $i = $i + 1;
            $accountInfo->{$optName} = $optVal;

            if ( $optVal =~ /^\s*(\d+)\s*:\s*(\w+)\/(.*?)\s*$/ ) {
                $passArgs->{$optName} = {
                    protocolPort => $1,
                    username     => $2,
                    password     => $3
                };
            }
            elsif ( $optVal =~ /^\s*(\w+)\/(.*?)\s*$/ ) {
                $passArgs->{$optName} = {
                    username => $1,
                    password => $2
                };
            }
        }
    }

    # Example:
    #Mysql:#{Mysql},Postgresql:#{Postgresql}
    #Mysql:root/pass1234,Postgresql:demo/test1234
    # 转换为:
    # {
    #     "Mysql":{
    #         "username":"root",
    #         "password":"mypassword"
    #     },
    #     "Postgresql":{
    #         "username":"pgroot",
    #         "password":"pgpassword"
    #     }
    # }
    if ( defined($defaultPassConf) and $defaultPassConf ne '' ) {
        $defaultPassConf =~ s/#\{\s*(\w+)\s*\}/$accountInfo->{$1}/g;
        $defaultPassConf = $defaultPassConf . ',H:H/H';

        while ( $defaultPassConf =~ /(.*?),(?=\w+:\w*\/)/g ) {
            my $part = $1;
            if ( $part =~ /^(\w+):(\w+)\/(.*)$/ ) {
                my $account = {};
                $account->{username} = $2;
                $account->{password} = $3;
                $passArgs->{$1}      = $account;
            }
        }
    }

    #收集OS和硬件信息
    my ( $osInfo, $hostInfo ) = collectHostOSInfo( $osType, $collectClassMap, $ignoreOsInfo, $inspect );
    my $ipv4Addrs   = $osInfo->{IPV4_ADDRS};
    my $ipv6Addrs = $osInfo->{IPV6_ADDRS};

    #获取收集网络信息的实现类
    my $connGather = ConnGather->new($inspect);

    #加载各个Collector，调用getConfig方法获取Process的filter配置
    my $procFilters = getProcessFilters($collectClassMap);

    my $pFinder   = ProcessFinder->new( $procFilters, connGather => $connGather, passArgs => $passArgs, osInfo => $osInfo, inspect => $inspect, bizIp => $osInfo->{BIZ_IP}, ipv4Addrs => $ipv4Addrs, ipv6Addrs => $ipv6Addrs, procEnvName => $procEnvName, container => $container );
    my $appsMap   = $pFinder->findProcess();
    my $appsArray = $pFinder->{appsArray};

    #处理存在父子关系的进程的连接信息，并合并到父进程
    #my $apps = mergeMultiProcs( $pFinder, $osInfo, $appsArray, $appsMap, $ipv4Addrs, $ipv6Addrs, $inspect );
    my $apps = $pFinder->mergeMultiProcs( $appsArray, $appsMap, $ipv4Addrs, $ipv6Addrs );

    #保存数据到output
    print("INFO: Begin to save collected data.\n");
    my @data = ();
    if ( $ignoreOsInfo == 0 ) {
        if (%$hostInfo) {
            push( @data, $hostInfo );
        }
        if (%$osInfo) {
            push( @data, $osInfo );
        }
    }
    push( @data, @$apps );

    my $out = {};
    $out->{DATA} = \@data;
    AutoExecUtils::saveOutput( $out, 1 );
    print("INFO: All are completed.\n");

    if ( $isVerbose == 1 ) {
        print("==================\n");
        print( to_json( $out->{DATA}, { pretty => 1 } ) );
        print("==================\n");
    }

    return 0;
}

exit main();
